package neptune.manager.allocator;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import neptune.manager.NeptuneManager;
import neptune.manager.connector.Connector;
import neptune.manager.connector.ConnectorException;
import neptune.manager.connector.LibvirtXenConnector;
import neptune.manager.model.ApplicationProperties;
import neptune.manager.model.cluster.Cluster;
import neptune.manager.model.cluster.PhysicalNode;
import neptune.manager.model.template.VmTemplate.OsDistributions;
import neptune.manager.model.topology.Node;
import neptune.manager.model.topology.NotEnoughResourcesException;
import neptune.manager.model.topology.Topology;
import neptune.manager.model.topology.UndefinedControlInterfaceException;
import neptune.manager.storage.StorageReadException;
import neptune.manager.storage.XMLApplicationPropertiesStorageFactory;
import neptune.manager.utils.DebugLog;

/**
 * 
 * @author Luigi Lorenzo Arpino [luigiarpino@yahoo.it]
 * 
 */
public class AsyncExperimentAllocatorMRF implements Runnable {

	private String scriptPath;
	private String vmTemplateSourcePath;
	private String experimentsVMsPath;
	private Topology topology;
	private Cluster cluster;
	private static int actualMachine = 0;
	private static final String IMG_EXTENSION = ".img";
	private String gatewayControlInterface; // il gateway dell'interfaccia di
											// controllo

	private Map<String, String[]> allocationMap;

	public AsyncExperimentAllocatorMRF(String emuPluginRootPath,
			String vmTemplatesPath, String experimentsVmsPath, String expId,
			Topology topology, Cluster cluster) throws AllocationException,
			StorageReadException {
		this.vmTemplateSourcePath = vmTemplatesPath;
		this.experimentsVMsPath = experimentsVmsPath;
		this.topology = topology;
		this.cluster = cluster;
		this.scriptPath = emuPluginRootPath + "scripts/clone.sh";
		String pathConfigFile = emuPluginRootPath + File.separator
				+ "configuration.xml";
		XMLApplicationPropertiesStorageFactory appPropFactory = new XMLApplicationPropertiesStorageFactory(
				pathConfigFile);
		ApplicationProperties appProperties = appPropFactory
				.getApplicationPropertiesStorage().getApplicationProperties();
		this.gatewayControlInterface = appProperties.getGatewayControlIpRange();
	}

	@Override
	public void run() {
		if (this.isRisorseDisponibili()) {
			this.retriveFreeRam();
			this.moreRamFirstAlgorithm();
			this.allocation();
		} else {

		}
	}

	private void retriveFreeRam() {

	}

	private String getIdOfMAchineWithMoreRam() {
		// TODO
		PhysicalNode machine = cluster.getNodes()[actualMachine];
		System.out.println("MRF=" + machine.getId());
		return machine.getId();
	}

	private void moreRamFirstAlgorithm() {
		allocationMap = new HashMap<String, String[]>(0);
		// Algoritmo
		for (Node node : topology.getNodes()) {
			String IDphysicalMachine = this.getIdOfMAchineWithMoreRam();
			String vmName = node.getId();
			String[] listVM = allocationMap.get(IDphysicalMachine);
			if (listVM == null) {
				allocationMap.put(IDphysicalMachine, new String[] { vmName });
			} else {
				String[] newListVMs = new String[listVM.length + 1];
				for (int i = 0; i < listVM.length; i++) {
					newListVMs[i] = listVM[i];
				}
				newListVMs[listVM.length] = vmName;
				allocationMap.put(IDphysicalMachine, newListVMs);
			}
		}

		/*
		 * pmLabel.getTitle()->1227977555600 vms[0]=WebServer vms[1]=Client1
		 * 
		 * pmLabel.getTitle()->1228923921049 vms[0]=Client2 vms[1]=Client3
		 */
		// allocationMap.put("ID_MACCHINA_FISICA",
		// "vettore si stringhe id macchine virtuali");
	}

	private void allocation() {
		// Aggiungo i task
		String taskId = String.valueOf(System.currentTimeMillis());
		for (String machineName : allocationMap.keySet()) {
			for (String vmName : allocationMap.get(machineName)) {
				Job job = new Job();
				job.setDescription("Deploy of virtual machine " + vmName);
				job.setId(vmName);
				job.setTaskId(taskId);
				JobQueue.getInstance().enqueue(job);
			}
		}

		for (String machineName : allocationMap.keySet()) {
			try {
				Connector connector = NeptuneManager.instance().getConnector(
						machineName);

				for (String vmName : allocationMap.get(machineName)) {

					Node node = topology.searchNodeIntoTopology(vmName);

					if (node == null) {
						String msg = "Node '" + vmName
								+ "' is undefined in topology '"
								+ topology.getName() + "'";
						new RuntimeException(msg);
						/*
						 * throw new AllocationException("Node '" + vmName +
						 * "' is undefined in topology '" + topology.getName() +
						 * "'");
						 */
					}
					String[] macAddresses = new String[node.getInterfaces().length];
					for (int i = 0; i < macAddresses.length; i++) {
						macAddresses[i] = node.getInterfaces()[i]
								.getMacAddress().getAddress();
					}

					String nodeFullId = node.getId()
							+ NeptuneManager.EXPID_NODEID_SEPARATOR
							+ topology.getName();

					int distribution;
					if (node.getOperativeSystem().getName().equals(
							OsDistributions.Debian.toString())) {
						distribution = LibvirtXenConnector.DIST_DEBIAN;
					} else if (node.getOperativeSystem().getName().equals(
							OsDistributions.CentOS.toString())) {
						distribution = LibvirtXenConnector.DIST_CENTOS;
					} else if (node.getOperativeSystem().getName().equals(
							OsDistributions.Fedora.toString())) {
						distribution = LibvirtXenConnector.DIST_FEDORA;
					} else {
						distribution = LibvirtXenConnector.DIST_UNDEFINED;
					}

					DebugLog.printDetailedLog("Connector creation",
							MainAllocator.class.getSimpleName());

					cluster.getNodeById(machineName).addHostedNode(node,
							topology.getName());
					Job job = null;
					try {
						// cambio lo stato del job
						job = JobQueue.getInstance().getJob(taskId, vmName);
						job.setState(Job.STATE_RUNNING);
						// creo la macchina virtuale
						connector.createVM(nodeFullId, node.getControlAddress()
								.getAddress(), node.getControlAddress()
								.getSubnetMask(), this.gatewayControlInterface,
								macAddresses, vmTemplateSourcePath
										+ node.getSoftwareTemplate()
										+ IMG_EXTENSION, experimentsVMsPath
										+ nodeFullId + IMG_EXTENSION,
								distribution, node.getRam(), scriptPath,
								"/sbin", "/sbin", null);
						// cambio lo stato del job
						job.setState(Job.STATE_FINISH);
					} catch (UndefinedControlInterfaceException e) {
						if (job != null) {
							job.setState(Job.STATE_FAILED);
						}
						// Elimina il riferimento all'allocazione di questo nodo
						cluster.getNodeById(machineName).removeHostedNode(node,
								topology.getName());

						DebugLog.printDetailedLog("Unable to create vm '"
								+ nodeFullId + "'. Cause: " + e.getMessage(),
								MainAllocator.class.getSimpleName());
					}
				}

				connector.closeConnection();

			} catch (ConnectorException e) {
				DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
						.getSimpleName());
				throw new RuntimeException(e);
			} catch (NotEnoughResourcesException e) {
				DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
						.getSimpleName());
				throw new RuntimeException(e);
			}
		}
	}

	private boolean isRisorseDisponibili() {
		if (this.cluster.getNodes().length == 0) {
			return false;
		}
		return true;
	}

	public static void main(String args[]) {
		Map<String, String[]> allocationMap = new HashMap<String, String[]>(0);
		allocationMap.put("ID1", new String[] { "Pippo", "Pluto" });
		System.out.println(allocationMap.get("ID1").toString());
		System.out.println(allocationMap.get("ID2"));

	}

}
